{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4cdade81-94de-42c5-a0ec-2f3520711618",
   "metadata": {},
   "source": [
    "# langGraph01： workflows and agent\n",
    "\n",
    "[sources from webwwite](https://langchain-ai.github.io/langgraph/tutorials/workflows/)\n",
    "\n",
    "**Workflows** are systems where LLMs and tools are orchestrated through predefined code paths. **Agents**, on the other hand, are systems where LLMs dynamically direct their own processes and tool usage, maintaining control over how they accomplish tasks.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d7c0537-3cd2-497e-a2bd-0257a5898869",
   "metadata": {},
   "source": [
    "![agent workflow](./datas/agent_workflow.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b545d30-2afd-4c3b-91d8-3c010593ed8b",
   "metadata": {},
   "source": [
    "## 1 prompt chaining"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dcc571bb-7de9-4beb-a476-e4604bf2a998",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from IPython.display import Image, display\n",
    "\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"deepseek/deepseek-chat-v3-0324:free\"\n",
    "  )\n",
    "\n",
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str\n",
    "    joke: str\n",
    "    improved_joke: str\n",
    "    final_joke: str\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def generate_joke(state: State):\n",
    "    \"\"\"First LLM call to generate initial joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a short joke about {state['topic']}\")\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def check_punchline(state: State):\n",
    "    \"\"\"Gate function to check if the joke has a punchline\"\"\"\n",
    "\n",
    "    # Simple check - does the joke contain \"?\" or \"!\"\n",
    "    if \"?\" in state[\"joke\"] or \"!\" in state[\"joke\"]:\n",
    "        return \"Fail\"\n",
    "    return \"Pass\"\n",
    "\n",
    "\n",
    "def improve_joke(state: State):\n",
    "    \"\"\"Second LLM call to improve the joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Make this joke funnier by adding wordplay: {state['joke']}\")\n",
    "    return {\"improved_joke\": msg.content}\n",
    "\n",
    "\n",
    "def polish_joke(state: State):\n",
    "    \"\"\"Third LLM call for final polish\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Add a surprising twist to this joke: {state['improved_joke']}\")\n",
    "    return {\"final_joke\": msg.content}\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "workflow = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "workflow.add_node(\"generate_joke\", generate_joke)\n",
    "workflow.add_node(\"improve_joke\", improve_joke)\n",
    "workflow.add_node(\"polish_joke\", polish_joke)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "workflow.add_edge(START, \"generate_joke\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"generate_joke\", check_punchline, {\"Fail\": \"improve_joke\", \"Pass\": END}\n",
    ")\n",
    "workflow.add_edge(\"improve_joke\", \"polish_joke\")\n",
    "workflow.add_edge(\"polish_joke\", END)\n",
    "\n",
    "# Compile\n",
    "chain = workflow.compile()\n",
    "\n",
    "# Show workflow\n",
    "display(Image(chain.get_graph().draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "state = chain.invoke({\"topic\": \"cats\"})\n",
    "print(\"Initial joke:\")\n",
    "print(state[\"joke\"])\n",
    "print(\"\\n--- --- ---\\n\")\n",
    "if \"improved_joke\" in state:\n",
    "    print(\"Improved joke:\")\n",
    "    print(state[\"improved_joke\"])\n",
    "    print(\"\\n--- --- ---\\n\")\n",
    "\n",
    "    print(\"Final joke:\")\n",
    "    print(state[\"final_joke\"])\n",
    "else:\n",
    "    print(\"Joke failed quality gate - no punchline detected!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08606b93-09c8-4a58-8bdb-54137a0bb318",
   "metadata": {},
   "source": [
    "## 2 parallelization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e755fcd0-df85-41df-b2f4-6c02dbcfedc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str\n",
    "    joke: str\n",
    "    story: str\n",
    "    poem: str\n",
    "    combined_output: str\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def call_llm_1(state: State):\n",
    "    \"\"\"First LLM call to generate initial joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a joke about {state['topic']}\")\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def call_llm_2(state: State):\n",
    "    \"\"\"Second LLM call to generate story\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a story about {state['topic']}\")\n",
    "    return {\"story\": msg.content}\n",
    "\n",
    "\n",
    "def call_llm_3(state: State):\n",
    "    \"\"\"Third LLM call to generate poem\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a poem about {state['topic']}\")\n",
    "    return {\"poem\": msg.content}\n",
    "\n",
    "\n",
    "def aggregator(state: State):\n",
    "    \"\"\"Combine the joke and story into a single output\"\"\"\n",
    "\n",
    "    combined = f\"Here's a story, joke, and poem about {state['topic']}!\\n\\n\"\n",
    "    combined += f\"STORY:\\n{state['story']}\\n\\n\"\n",
    "    combined += f\"JOKE:\\n{state['joke']}\\n\\n\"\n",
    "    combined += f\"POEM:\\n{state['poem']}\"\n",
    "    return {\"combined_output\": combined}\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "parallel_builder = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "parallel_builder.add_node(\"call_llm_1\", call_llm_1)\n",
    "parallel_builder.add_node(\"call_llm_2\", call_llm_2)\n",
    "parallel_builder.add_node(\"call_llm_3\", call_llm_3)\n",
    "parallel_builder.add_node(\"aggregator\", aggregator)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "parallel_builder.add_edge(START, \"call_llm_1\")\n",
    "parallel_builder.add_edge(START, \"call_llm_2\")\n",
    "parallel_builder.add_edge(START, \"call_llm_3\")\n",
    "parallel_builder.add_edge(\"call_llm_1\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"call_llm_2\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"call_llm_3\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"aggregator\", END)\n",
    "parallel_workflow = parallel_builder.compile()\n",
    "\n",
    "# Show workflow\n",
    "display(Image(parallel_workflow.get_graph().draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "state = parallel_workflow.invoke({\"topic\": \"cats\"})\n",
    "print(state[\"combined_output\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00451ced-cd26-4e2e-8c7a-f35349a6e4ad",
   "metadata": {},
   "source": [
    "## 3 routing\n",
    "\n",
    "llm genini support structured output adn free."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "131246ba-6143-4b5a-80f3-a68ec5959eff",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing_extensions import Literal\n",
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "\n",
    "from langchain_openai import ChatOpenAI\n",
    "# deepseek does not support structed output,so find another llm\n",
    "llm = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"google/gemini-2.5-pro-exp-03-25:free\"\n",
    "  )\n",
    "# Schema for structured output to use as routing logic\n",
    "class Route(BaseModel):\n",
    "    step: Literal[\"poem\", \"story\", \"joke\"] = Field(\n",
    "        None, description=\"The next step in the routing process\"\n",
    "    )\n",
    "\n",
    "\n",
    "# Augment the LLM with schema for structured output\n",
    "router = llm.with_structured_output(Route)\n",
    "\n",
    "\n",
    "# State\n",
    "class State(TypedDict):\n",
    "    input: str\n",
    "    decision: str\n",
    "    output: str\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def llm_call_1(state: State):\n",
    "    \"\"\"Write a story\"\"\"\n",
    "\n",
    "    result = llm.invoke(state[\"input\"])\n",
    "    return {\"output\": result.content}\n",
    "\n",
    "\n",
    "def llm_call_2(state: State):\n",
    "    \"\"\"Write a joke\"\"\"\n",
    "\n",
    "    result = llm.invoke(state[\"input\"])\n",
    "    return {\"output\": result.content}\n",
    "\n",
    "\n",
    "def llm_call_3(state: State):\n",
    "    \"\"\"Write a poem\"\"\"\n",
    "\n",
    "    result = llm.invoke(state[\"input\"])\n",
    "    return {\"output\": result.content}\n",
    "\n",
    "\n",
    "def llm_call_router(state: State):\n",
    "    \"\"\"Route the input to the appropriate node\"\"\"\n",
    "\n",
    "    # Run the augmented LLM with structured output to serve as routing logic\n",
    "    decision = router.invoke(\n",
    "        [\n",
    "            SystemMessage(\n",
    "                content=\"Route the input to story, joke, or poem based on the user's request.\"\n",
    "            ),\n",
    "            HumanMessage(content=state[\"input\"]),\n",
    "        ]\n",
    "    )\n",
    "    print(decision)\n",
    "\n",
    "    return {\"decision\": decision.step}\n",
    "\n",
    "\n",
    "# Conditional edge function to route to the appropriate node\n",
    "def route_decision(state: State):\n",
    "    # Return the node name you want to visit next\n",
    "    if state[\"decision\"] == \"story\":\n",
    "        return \"llm_call_1\"\n",
    "    elif state[\"decision\"] == \"joke\":\n",
    "        return \"llm_call_2\"\n",
    "    elif state[\"decision\"] == \"poem\":\n",
    "        return \"llm_call_3\"\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "router_builder = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "router_builder.add_node(\"llm_call_1\", llm_call_1)\n",
    "router_builder.add_node(\"llm_call_2\", llm_call_2)\n",
    "router_builder.add_node(\"llm_call_3\", llm_call_3)\n",
    "router_builder.add_node(\"llm_call_router\", llm_call_router)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "router_builder.add_edge(START, \"llm_call_router\")\n",
    "router_builder.add_conditional_edges(\n",
    "    \"llm_call_router\",\n",
    "    route_decision,\n",
    "    {  # Name returned by route_decision : Name of next node to visit\n",
    "        \"llm_call_1\": \"llm_call_1\",\n",
    "        \"llm_call_2\": \"llm_call_2\",\n",
    "        \"llm_call_3\": \"llm_call_3\",\n",
    "    },\n",
    ")\n",
    "router_builder.add_edge(\"llm_call_1\", END)\n",
    "router_builder.add_edge(\"llm_call_2\", END)\n",
    "router_builder.add_edge(\"llm_call_3\", END)\n",
    "\n",
    "# Compile workflow\n",
    "router_workflow = router_builder.compile()\n",
    "\n",
    "# Show the workflow\n",
    "display(Image(router_workflow.get_graph().draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "state = router_workflow.invoke({\"input\": \"Write me a poem about cats\"})\n",
    "print(state[\"output\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f39a4e89-ecf4-4242-b2d9-d3c57bb2c96d",
   "metadata": {},
   "source": [
    "## 4 orchestrator-worker workflows"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6badf60c-018a-48a5-b720-20fcb8ea255a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, List\n",
    "import operator\n",
    "from langchain_openai import ChatOpenAI\n",
    "# deepseek does not support structed output,so find another llm\n",
    "llm = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"google/gemini-2.5-pro-exp-03-25:free\"\n",
    "  )\n",
    "llm_worker = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"google/gemini-2.5-pro-exp-03-25:free\"\n",
    "  )\n",
    "# Schema for structured output to use in planning\n",
    "class Section(BaseModel):\n",
    "    name: str = Field(\n",
    "        description=\"Name for this section of the report.\",\n",
    "    )\n",
    "    description: str = Field(\n",
    "        description=\"Brief overview of the main topics and concepts to be covered in this section.\",\n",
    "    )\n",
    "\n",
    "\n",
    "class Sections(BaseModel):\n",
    "    sections: List[Section] = Field(\n",
    "        description=\"Sections of the report.\",\n",
    "    )\n",
    "\n",
    "\n",
    "# Augment the LLM with schema for structured output\n",
    "planner = llm.with_structured_output(Sections)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "176009d1-bed8-4717-b90d-d8abe9ee5a61",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.constants import Send\n",
    "\n",
    "\n",
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str  # Report topic\n",
    "    sections: list[Section]  # List of report sections\n",
    "    completed_sections: Annotated[\n",
    "        list, operator.add\n",
    "    ]  # All workers write to this key in parallel\n",
    "    final_report: str  # Final report\n",
    "\n",
    "\n",
    "# Worker state\n",
    "class WorkerState(TypedDict):\n",
    "    section: Section\n",
    "    completed_sections: Annotated[list, operator.add]\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def orchestrator(state: State):\n",
    "    \"\"\"Orchestrator that generates a plan for the report\"\"\"\n",
    "\n",
    "    # Generate queries\n",
    "    report_sections = planner.invoke(\n",
    "        [\n",
    "            SystemMessage(content=\"Generate a plan for the report.\"),\n",
    "            HumanMessage(content=f\"Here is the report topic: {state['topic']}\"),\n",
    "        ]\n",
    "    )\n",
    "\n",
    "    print(report_sections.sections)\n",
    "    return {\"sections\": report_sections.sections}\n",
    "\n",
    "\n",
    "def llm_call(state: WorkerState):\n",
    "    \"\"\"Worker writes a section of the report\"\"\"\n",
    "\n",
    "    # Generate section\n",
    "    section = llm_worker.invoke(\n",
    "        [\n",
    "            SystemMessage(\n",
    "                content=\"Write a report section following the provided name and description. Include no preamble for each section. Use markdown formatting.\"\n",
    "            ),\n",
    "            HumanMessage(\n",
    "                content=f\"Here is the section name: {state['section'].name} and description: {state['section'].description}\"\n",
    "            ),\n",
    "        ]\n",
    "    )\n",
    "\n",
    "    # Write the updated section to completed sections\n",
    "    return {\"completed_sections\": [section.content]}\n",
    "\n",
    "\n",
    "def synthesizer(state: State):\n",
    "    \"\"\"Synthesize full report from sections\"\"\"\n",
    "\n",
    "    # List of completed sections\n",
    "    completed_sections = state[\"completed_sections\"]\n",
    "\n",
    "    # Format completed section to str to use as context for final sections\n",
    "    completed_report_sections = \"\\n\\n---\\n\\n\".join(completed_sections)\n",
    "\n",
    "    return {\"final_report\": completed_report_sections}\n",
    "\n",
    "\n",
    "# Conditional edge function to create llm_call workers that each write a section of the report\n",
    "def assign_workers(state: State):\n",
    "    \"\"\"Assign a worker to each section in the plan\"\"\"\n",
    "\n",
    "    # Kick off section writing in parallel via Send() API\n",
    "    return [Send(\"llm_call\", {\"section\": s}) for s in state[\"sections\"]]\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "orchestrator_worker_builder = StateGraph(State)\n",
    "\n",
    "# Add the nodes\n",
    "orchestrator_worker_builder.add_node(\"orchestrator\", orchestrator)\n",
    "orchestrator_worker_builder.add_node(\"llm_call\", llm_call)\n",
    "orchestrator_worker_builder.add_node(\"synthesizer\", synthesizer)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "orchestrator_worker_builder.add_edge(START, \"orchestrator\")\n",
    "orchestrator_worker_builder.add_conditional_edges(\n",
    "    \"orchestrator\", assign_workers, [\"llm_call\"]\n",
    ")\n",
    "orchestrator_worker_builder.add_edge(\"llm_call\", \"synthesizer\")\n",
    "orchestrator_worker_builder.add_edge(\"synthesizer\", END)\n",
    "\n",
    "# Compile the workflow\n",
    "orchestrator_worker = orchestrator_worker_builder.compile()\n",
    "\n",
    "# Show the workflow\n",
    "display(Image(orchestrator_worker.get_graph().draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "state = orchestrator_worker.invoke({\"topic\": \"Create a report on LLM scaling laws\"})\n",
    "\n",
    "from IPython.display import Markdown\n",
    "Markdown(state[\"final_report\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1924bc64-f335-456b-ac04-3b929b79b4fd",
   "metadata": {},
   "source": [
    "## 5 Evaluator-optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44bef275-21ef-454c-ad9a-240a2df038ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    joke: str\n",
    "    topic: str\n",
    "    feedback: str\n",
    "    funny_or_not: str\n",
    "\n",
    "\n",
    "# Schema for structured output to use in evaluation\n",
    "class Feedback(BaseModel):\n",
    "    grade: Literal[\"funny\", \"not funny\"] = Field(\n",
    "        description=\"Decide if the joke is funny or not.\",\n",
    "    )\n",
    "    feedback: str = Field(\n",
    "        description=\"If the joke is not funny, provide feedback on how to improve it.\",\n",
    "    )\n",
    "\n",
    "\n",
    "# Augment the LLM with schema for structured output\n",
    "from langchain_openai import ChatOpenAI\n",
    "# deepseek does not support structed output,so find another llm\n",
    "llm_gen = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"deepseek/deepseek-v3-base:free\"\n",
    "  )\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"google/gemini-2.5-pro-exp-03-25:free\"\n",
    "  )\n",
    "evaluator = llm.with_structured_output(Feedback)\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def llm_call_generator(state: State):\n",
    "    \"\"\"LLM generates a joke\"\"\"\n",
    "\n",
    "    if state.get(\"feedback\"):\n",
    "        msg = llm_gen.invoke(\n",
    "            f\"Write a joke about {state['topic']} but take into account the feedback: {state['feedback']}\"\n",
    "        )\n",
    "    else:\n",
    "        msg = llm_gen.invoke(f\"Write a joke about {state['topic']}\")\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def llm_call_evaluator(state: State):\n",
    "    \"\"\"LLM evaluates the joke\"\"\"\n",
    "\n",
    "    grade = evaluator.invoke(f\"Grade the joke {state['joke']}\")\n",
    "    return {\"funny_or_not\": grade.grade, \"feedback\": grade.feedback}\n",
    "\n",
    "\n",
    "# Conditional edge function to route back to joke generator or end based upon feedback from the evaluator\n",
    "def route_joke(state: State):\n",
    "    \"\"\"Route back to joke generator or end based upon feedback from the evaluator\"\"\"\n",
    "\n",
    "    if state[\"funny_or_not\"] == \"funny\":\n",
    "        return \"Accepted\"\n",
    "    elif state[\"funny_or_not\"] == \"not funny\":\n",
    "        return \"Rejected + Feedback\"\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "optimizer_builder = StateGraph(State)\n",
    "\n",
    "# Add the nodes\n",
    "optimizer_builder.add_node(\"llm_call_generator\", llm_call_generator)\n",
    "optimizer_builder.add_node(\"llm_call_evaluator\", llm_call_evaluator)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "optimizer_builder.add_edge(START, \"llm_call_generator\")\n",
    "optimizer_builder.add_edge(\"llm_call_generator\", \"llm_call_evaluator\")\n",
    "optimizer_builder.add_conditional_edges(\n",
    "    \"llm_call_evaluator\",\n",
    "    route_joke,\n",
    "    {  # Name returned by route_joke : Name of next node to visit\n",
    "        \"Accepted\": END,\n",
    "        \"Rejected + Feedback\": \"llm_call_generator\",\n",
    "    },\n",
    ")\n",
    "\n",
    "# Compile the workflow\n",
    "optimizer_workflow = optimizer_builder.compile()\n",
    "\n",
    "# Show the workflow\n",
    "display(Image(optimizer_workflow.get_graph().draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "state = optimizer_workflow.invoke({\"topic\": \"Cats\"})\n",
    "print(state[\"joke\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f863ada-2352-440e-8402-85d3f4e45eb0",
   "metadata": {},
   "source": [
    "## 6 Agent\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "fa4d2bd5-3a2e-4e8d-a7ff-a8734ae090d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "from langchain_openai import ChatOpenAI\n",
    "llm = ChatOpenAI(\n",
    "  openai_api_key=\"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\",\n",
    "  openai_api_base=\"https://openrouter.ai/api/v1\",\n",
    "  model_name = \"google/gemini-2.0-flash-exp:free\"\n",
    "  )\n",
    "\n",
    "\n",
    "# Define tools\n",
    "@tool\n",
    "def multiply(a: int, b: int) -> int:\n",
    "    \"\"\"Multiply a and b.\n",
    "\n",
    "    Args:\n",
    "        a: first int\n",
    "        b: second int\n",
    "    \"\"\"\n",
    "    return a * b\n",
    "\n",
    "\n",
    "@tool\n",
    "def add(a: int, b: int) -> int:\n",
    "    \"\"\"Adds a and b.\n",
    "\n",
    "    Args:\n",
    "        a: first int\n",
    "        b: second int\n",
    "    \"\"\"\n",
    "    return a + b\n",
    "\n",
    "\n",
    "@tool\n",
    "def divide(a: int, b: int) -> float:\n",
    "    \"\"\"Divide a and b.\n",
    "\n",
    "    Args:\n",
    "        a: first int\n",
    "        b: second int\n",
    "    \"\"\"\n",
    "    return a / b\n",
    "\n",
    "\n",
    "# Augment the LLM with tools\n",
    "tools = [add, multiply, divide]\n",
    "tools_by_name = {tool.name: tool for tool in tools}\n",
    "llm_with_tools = llm.bind_tools(tools)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a48e7af-ff29-49b8-bdc8-3346b1373b9e",
   "metadata": {},
   "source": [
    "### 6.1 normal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "e37fbbb3-70d5-4991-8e03-6086029f2cc0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Add 3 and 4.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  add (tool_0_add)\n",
      " Call ID: tool_0_add\n",
      "  Args:\n",
      "    b: 4\n",
      "    a: 3\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "7\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "7\n"
     ]
    }
   ],
   "source": [
    "from langgraph.graph import MessagesState\n",
    "from langchain_core.messages import SystemMessage, HumanMessage, ToolMessage\n",
    "from typing_extensions import Literal\n",
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from IPython.display import Image, display\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def llm_call(state: MessagesState):\n",
    "    \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
    "\n",
    "    return {\n",
    "        \"messages\": [\n",
    "            llm_with_tools.invoke(\n",
    "                [\n",
    "                    SystemMessage(\n",
    "                        content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\"\n",
    "                    )\n",
    "                ]\n",
    "                + state[\"messages\"]\n",
    "            )\n",
    "        ]\n",
    "    }\n",
    "\n",
    "\n",
    "def tool_node(state: dict):\n",
    "    \"\"\"Performs the tool call\"\"\"\n",
    "\n",
    "    result = []\n",
    "    for tool_call in state[\"messages\"][-1].tool_calls:\n",
    "        tool = tools_by_name[tool_call[\"name\"]]\n",
    "        observation = tool.invoke(tool_call[\"args\"])\n",
    "        result.append(ToolMessage(content=observation, tool_call_id=tool_call[\"id\"]))\n",
    "    return {\"messages\": result}\n",
    "\n",
    "\n",
    "# Conditional edge function to route to the tool node or end based upon whether the LLM made a tool call\n",
    "def should_continue(state: MessagesState) -> Literal[\"environment\", END]:\n",
    "    \"\"\"Decide if we should continue the loop or stop based upon whether the LLM made a tool call\"\"\"\n",
    "\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    # If the LLM makes a tool call, then perform an action\n",
    "    if last_message.tool_calls:\n",
    "        return \"Action\"\n",
    "    # Otherwise, we stop (reply to the user)\n",
    "    return END\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "agent_builder = StateGraph(MessagesState)\n",
    "\n",
    "# Add nodes\n",
    "agent_builder.add_node(\"llm_call\", llm_call)\n",
    "agent_builder.add_node(\"environment\", tool_node)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "agent_builder.add_edge(START, \"llm_call\")\n",
    "agent_builder.add_conditional_edges(\n",
    "    \"llm_call\",\n",
    "    should_continue,\n",
    "    {\n",
    "        # Name returned by should_continue : Name of next node to visit\n",
    "        \"Action\": \"environment\",\n",
    "        END: END,\n",
    "    },\n",
    ")\n",
    "agent_builder.add_edge(\"environment\", \"llm_call\")\n",
    "\n",
    "# Compile the agent\n",
    "agent = agent_builder.compile()\n",
    "\n",
    "# Show the agent\n",
    "display(Image(agent.get_graph(xray=True).draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "messages = [HumanMessage(content=\"Add 3 and 4.\")]\n",
    "messages = agent.invoke({\"messages\": messages})\n",
    "for m in messages[\"messages\"]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ac38369-3459-43fc-bc96-82a7005e25d5",
   "metadata": {},
   "source": [
    "### 6.2 create agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "acd1802e-e048-4dd4-9c75-ecc55652d5b0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Add 3 and 4.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  add (tool_0_add)\n",
      " Call ID: tool_0_add\n",
      "  Args:\n",
      "    b: 4\n",
      "    a: 3\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: add\n",
      "\n",
      "7\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "The sum of 3 and 4 is 7.\n"
     ]
    }
   ],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "# Pass in:\n",
    "# (1) the augmented LLM with tools\n",
    "# (2) the tools list (which is used to create the tool node)\n",
    "pre_built_agent = create_react_agent(llm, tools=tools)\n",
    "\n",
    "# Show the agent\n",
    "display(Image(pre_built_agent.get_graph().draw_mermaid_png()))\n",
    "\n",
    "# Invoke\n",
    "messages = [HumanMessage(content=\"Add 3 and 4.\")]\n",
    "messages = pre_built_agent.invoke({\"messages\": messages})\n",
    "for m in messages[\"messages\"]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec7a9586-c358-43c4-9e1f-4c057c72f559",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
